//
//  OptionalChain.swift
//  cnswift-demo
//
//  Created by YangWei on 2021/1/2.
//  Copyright © 2021 YangWei. All rights reserved.
//

import UIKit

class OptionalChaining: SWDBaseObject {
    
    class func run() {
        let optionalChaining = OptionalChaining()
        
        // 1
        optionalChaining.optionalChainingAsAnAlternativeToForcedUnwrapping()
        
        // 2
        optionalChaining.definingModelClassesForOptionalChaining()
        
        // 3
        optionalChaining.accessingPropertiesThroughOptionalChaining()
        
        // 4
        optionalChaining.callingMethodsThroughOptionalChaining()
        
        // 5
        optionalChaining.accessingSubscriptsThroughOptionalChaining()
        
        // 5.1
        optionalChaining.accessingSubscriptsOfOptionalType()
        
        // 6
        optionalChaining.linkingMultipleLevelsOfChaining()
        
        // 6.1
        optionalChaining.chainingOnMethodsWithOptionalReturnValues()
    }
    
    /// 1 使用可选链式调用代替强制解包
    func optionalChainingAsAnAlternativeToForcedUnwrapping() {
        let title = "1 使用可选链式调用代替强制解包";
        printStartLine(title)
        
        // 创建一个新的 Person 实例，residence 属性由于是可选类型而将被初始化为 nil
        let john = Person()
        
        let testForcedUnwrapping = false //true
        // john 有一个值为 nil 的 residence 属性：
        if testForcedUnwrapping {
            // 使用（!）强制解包 residence 属性的 numberOfRooms 值，会触发运行时错误（residence 没有可以解包的值）
            // 这会引发运行时错误
            let roomCount = john.residence!.numberOfRooms
        }
        
        // 可选链式调用另一种访问 numberOfRooms 的方式，使用问号（?）来替代原来的叹号（!）：
        if let roomCount = john.residence?.numberOfRooms {
            print("John's residence has \(roomCount) room(s).")
        } else {
            print("Unable to retrieve the number of rooms.")
        }
        // 打印“Unable to retrieve the number of rooms.”

        // 将一个 Residence 实例赋给 john.residence，这样 john.residence 就不再是 nil ：
        john.residence = Residence()

        // 问号之后，Swift 会在 residence 不为 nil 的情况下访问 numberOfRooms。
        // 使用可选链式调用访问 numberOfRooms，现在返回值为 1 的 Int? 类型的值：
        if let roomCount = john.residence?.numberOfRooms {
            print("John's residence has \(roomCount) room(s).")
        } else {
            print("Unable to retrieve the number of rooms.")
        }
        // 打印“John's residence has 1 room(s).”
        
        printEndLine(title)
    }
    
    /// 2 为可选链式调用定义模型类
    func definingModelClassesForOptionalChaining() {
        let title = "2 为可选链式调用定义模型类";
        printStartLine(title)
        
        // 查看 PersonV2、ResidenceV2、Room、Address
        
        printEndLine(title)
    }
    
    /// 3 通过可选链式调用访问属性
    func accessingPropertiesThroughOptionalChaining() {
        let title = "3 通过可选链式调用访问属性";
        printStartLine(title)
        
        // 创建一个 Person 实例，尝试访问 numberOfRooms 属性：
        let john = PersonV2()
        // john.residence 为 nil
        if let roomCount = john.residence?.numberOfRooms {
            print("John's residence has \(roomCount) room(s).")
        } else {
            print("Unable to retrieve the number of rooms.")
        }
        // 打印“Unable to retrieve the number of rooms.”

        // 通过可选链式调用来设置属性值
        let someAddress = Address()
        someAddress.buildingNumber = "29"
        someAddress.street = "Acacia Road"
        // 设定 address 属性也会失败，因为 john.residence 当前为 nil
        john.residence?.address = someAddress
        print(john.residence as Any)
        
        // 验证可选链式调用失败时，等号右侧的代码不会被执行
        john.residence?.address = createAddress()
        // createAddress() 函数没有被调用，说明 john.residence ？后面的代码没有被执行。
        
        printEndLine(title)
    }
    
    // 使用一个函数来创建 Address 实例，然后将该实例返回
    func createAddress() -> Address {
        // 返回前打印“Function was called”，这使你能验证等号右侧的代码是否被执行
        print("Function was called.")
        
        let someAddress = Address()
        someAddress.buildingNumber = "29"
        someAddress.street = "Acacia Road"
        
        return someAddress
    }
    
    /// 4 通过可选链式调用来调用方法
    func callingMethodsThroughOptionalChaining() {
        let title = "4 通过可选链式调用来调用方法";
        printStartLine(title)
        
        let john = PersonV2()
        
        // 通过判断返回值是否为 nil 可以判断调用是否成功：
        if john.residence?.printNumberOfRooms() != nil {
            print("It was possible to print the number of rooms.")
        } else {
            print("It was not possible to print the number of rooms.")
        }
        // 打印“It was not possible to print the number of rooms.”

        if (john.residence?.address = createAddress()) != nil {
            print("It was possible to set the address.")
        } else {
            print("It was not possible to set the address.")
        }
        // 打印“It was not possible to set the address.”
        
        printEndLine(title)
    }
    
    /// 5 通过可选链式调用访问下标
    func accessingSubscriptsThroughOptionalChaining() {
        let title = "5 通过可选链式调用访问下标";
        printStartLine(title)
        
        let john = PersonV2()
        
        // 访问属性存储的 Residence 实例的 rooms 数组中的第一个房间的名称
        if let firstRoomName = john.residence?[0].name {
            print("The first room name is \(firstRoomName).")
        } else {
            print("Unable to retrieve the first room name.")
        }
        // 打印“Unable to retrieve the first room name.”

        // 通过下标，用可选链式调用来赋值：失败，residence 还是 nil
        john.residence?[0] = Room(name: "Bathroom")

        // 创建一个 Residence 实例
        let johnsHouse = ResidenceV2()
        // 为其 rooms 数组添加一些 Room 实例
        johnsHouse.rooms.append(Room(name: "Living Room"))
        johnsHouse.rooms.append(Room(name: "Kitchen"))
        // 将 Residence 实例赋值给 john.residence
        john.residence = johnsHouse
         
        // 通过可选链和下标来访问数组中的元素：
        if let firstRoomName = john.residence?[0].name {
            print("The first room name is \(firstRoomName).")
        } else {
            print("Unable to retrieve the first room name.")
        }
        // 打印“The first room name is Living Room.”
        
        printEndLine(title)
    }
    
    /// 5.1 访问可选类型的下标
    func accessingSubscriptsOfOptionalType() {
        let title = "5.1 访问可选类型的下标";
        printStartLine(title)
        
        // 分别把 String 类型的键映射到一个 Int 值的数组。
        var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
        // 用可选链式调用把 "Dave" 数组中第一个元素设为 91:调用成功
        testScores["Dave"]?[0] = 91
        // 用可选链式调用把 "Bev" 数组的第一个元素 +1:调用成功
        testScores["Bev"]?[0] += 1
        // 用可选链式调用尝试把 "Brian" 数组中的第一个元素设为 72:调用失败，字典中没有 "Brian" 这个键
        testScores["Brian"]?[0] = 72
        // "Dave" 数组现在是 [91, 82, 84]，"Bev" 数组现在是 [80, 94, 81]
        
        print(testScores)
        
        printEndLine(title)
    }
    
    /// 6 连接多层可选链式调用
    func linkingMultipleLevelsOfChaining() {
        let title = "6 连接多层可选链式调用";
        printStartLine(title)
        
        let john = PersonV2()
        
        // 创建一个 Residence 实例
        let johnsHouse = ResidenceV2()
        // 为其 rooms 数组添加一些 Room 实例
        johnsHouse.rooms.append(Room(name: "Living Room"))
        johnsHouse.rooms.append(Room(name: "Kitchen"))
        // 将 Residence 实例赋值给 john.residence
        john.residence = johnsHouse
        
        // 访问 john 中的 residence 属性中的 address 属性中的 street 属性
        // 使用了两层可选链式调用，residence 以及 address 都是可选值
        if let johnsStreet = john.residence?.address?.street {
            print("John's street name is \(johnsStreet).")
        } else {
            // john.residence.address 的值当前为 nil，调用 john.residence?.address?.street 会失败
            print("Unable to retrieve the address.")
        }
        // 打印“Unable to retrieve the address.”
        
        // 为 john.residence.address 赋值一个 Address 实例
        let johnsAddress = Address()
        johnsAddress.buildingName = "The Larches"
        // 为 address 中的 street 属性设置一个有效值
        johnsAddress.street = "Laurel Street"
        john.residence?.address = johnsAddress

        // 通过可选链式调用来访问 street 属性：
        if let johnsStreet = john.residence?.address?.street {
            print("John's street name is \(johnsStreet).")
        } else {
            print("Unable to retrieve the address.")
        }
        // 打印“John's street name is Laurel Street.”
        
        printEndLine(title)
    }
    
    /// 6.1 在方法的可选返回值上进行可选链接调用
    func chainingOnMethodsWithOptionalReturnValues() {
        let title = "6.1 在方法的可选返回值上进行可选链接调用";
        printStartLine(title)
        
        let john = PersonV2()
        
        // 创建一个 Residence 实例
        let johnsHouse = ResidenceV2()
        // 为其 rooms 数组添加一些 Room 实例
        johnsHouse.rooms.append(Room(name: "Living Room"))
        johnsHouse.rooms.append(Room(name: "Kitchen"))
        // 将 Residence 实例赋值给 john.residence
        john.residence = johnsHouse
        
        // 为 john.residence.address 赋值一个 Address 实例
        let johnsAddress = Address()
        johnsAddress.buildingName = "The Larches"
        // 为 address 中的 street 属性设置一个有效值
        johnsAddress.street = "Laurel Street"
        john.residence?.address = johnsAddress

        // 通过可选链式调用来调用 Address 的 buildingIdentifier() 方法，返回 String? 类型的值
        if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
            print("John's building identifier is \(buildingIdentifier).")
        }
        // 打印“John's building identifier is The Larches.”

        // 在该方法的返回值上继续可选链式调用，在方法的圆括号后面加上问号即可
        if let beginsWithThe =
            john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
                if beginsWithThe {
                    print("John's building identifier begins with \"The\".")
                } else {
                    print("John's building identifier does not begin with \"The\".")
                }
        }
        // 打印“John's building identifier begins with "The".”
        
        printEndLine(title)
    }
}

/// 1 使用可选链式调用代替强制解包
/// 人
class Person {
    // 一个可选的 residence 属性，其类型为 Residence?
    var residence: Residence?
}

/// 1 使用可选链式调用代替强制解包
/// 住宅
class Residence {
    // 一个 Int 类型的属性 numberOfRooms，其默认值为 1
    var numberOfRooms = 1
}

/// 2 为可选链式调用定义模型类
/// 人
class PersonV2 {
    // 一个可选的 residence 属性，其类型为 Residence?
    var residence: ResidenceV2?
}

/// 2 为可选链式调用定义模型类
/// 住所住宅
class ResidenceV2 {
    // 变量属性，使用一个空的 [Room] 类型空数组初始化，存储 Room 实例
    var rooms = [Room]()
    
    // 计算型属性
    var numberOfRooms: Int {
        // 返回 rooms 数组的 count 属性的值
        return rooms.count
    }
    
    // 访问 rooms 数组的快捷方式，提供可读写的下标来访问 rooms 数组中指定位置的元素
    subscript(i: Int) -> Room {
        get {
            return rooms[i]
        }
        set {
            rooms[i] = newValue
        }
    }
    
    // 打印 numberOfRooms 的值
    // 方法没有返回值，具有隐式的返回类型 Void，没有返回值的方法也会返回 ()，或者说空的元组
    // 在可选值上通过可选链式调用来调用这个方法，该方法的返回类型会是 Void?，而不是 Void
    func printNumberOfRooms() {
        // 打印当前的 numberOfRooms 值
        print("The number of rooms is \(numberOfRooms)")
    }
    
    // 可选属性 address，其类型为 Address?
    var address: Address?
}

class Room {
    let name: String
    // 设置适当的房间名的初始化函数
    init(name: String) { self.name = name }
}

class Address {
    // 大厦名称（String? 类型的可选属性）
    var buildingName: String?
    // 大厦号码（String? 类型的可选属性）
    var buildingNumber: String?
    // 大厦所在街道的名称（String? 类型的可选属性）
    var street: String?
    
    // buildingName 有值则返回 buildingName。
    // 或者 buildingNumber 和 street 均有值，则返回两者拼接得到的字符串。
    // 否则，返回 nil
    func buildingIdentifier() -> String? {
        if buildingName != nil {
            return buildingName
        } else if buildingNumber != nil && street != nil {
            return "\(buildingNumber) \(street)"
        } else {
            return nil
        }
    }
}
